from pygame.sprite import Sprite,Group
from udp_net import *
from player import Player
from gameobjects import *
from ship import *
from weapons import *
from common import *
from netmessages import *
from types import *
#---------------------------------------------------------------
# NetController
#---------------------------------------------------------------
class DummyNetController:
    class Error(Exception):
        pass

    def __init__(self,game_system=None,my_port=None,rem_addr=None):
        '''
        Dummy Network handling object.
        '''
        self.id = 0
        self.is_server = 0
        self.is_client = 0
        self.game_system = game_system
        print "No network play"
        
    def getMyAddr(self):
        return "127.0.0.1"
    def setGameEngine(self,ge):
        self.game_engine = ge

    # Server Methods
    def tellPeer(self,peer,net_mess):
        pass
    def tellAllPeers(self,net_mess):
        pass
    def sysTellAllPeers(self,net_mess):
        pass
    def close(self):
        pass
    def syncPeers(self):
        pass
    def checkPeers(self):
        pass
    def reset(self,kickpeers=1):
        pass
    def ping(self,dt):
        pass
    # Client Methods
    def update(self):
        pass

#---------------------------------------------------------------
# NetController
#---------------------------------------------------------------
class NetController(DummyNetController):
    def __init__(self,game_system,my_port,log=sys.stderr):
        '''
        Main Network handling object. Parent base class to both client and server gameengines,
    works like a wrapper around gameengins to faciliate RPC.
        '''
        self.id = 0

        self.game_system = game_system
        self.log = log
        self.net_mess_lock = threading.Lock()

        self.game_engine = None

        self.net_messages = []
        
        self.peers = GameObjectGroup(()) # all connected clients
        self.peers_running = GameObjectGroup(()) # clients with a running Game Engine

        self.callback = Callback(self,log)

        # Network Keep alive ping
        self.ping_tick = Tick(3.0)

        self.lastrecv = 0    # number of last recived netmess

    def addPeer(self,addr):
        Peer.ID +=1
        id = Peer.ID
        for peer in self.peers.sprites():
            if peer.addr == addr:
                self.log.write("Peer %d Reconnecting\n"%peer.id)
                peer.kill()
        self.log.write( "----- Add Peer %d-----\n"%id)
        net_mess = NetMessSetPeerId(self.id,None,id)
        self.net.sendNetMessTo(addr,net_mess.getList())
        peer = Peer(self,addr,id)
        self.peers.add((peer,))


    def setGameEngine(self,ge):
        self.game_engine = ge
        self.callback.game_engine = ge

    def getMyAddr(self):
        return self.net.getMyAddr()


    def tellPeer(self,peer,net_mess):
        peer.send(net_mess)

    def tellAllPeers(self,net_mess):
        ' tell all peers with running game engine, but dont tell the peer the net mess is from '
        print "net mess",net_mess
        for peer in self.peers_running.sprites():
            if net_mess.mess_from != peer.id:
                peer.send(net_mess)

    def sysTellAllPeers(self,net_mess):
        ' tell all peers, but dont tell the peer the net mess is from '
        for peer in self.peers.sprites():
            if net_mess.mess_from != peer.id:
                peer.send(net_mess)

    def checkPeers(self):
        ' Check if peers timed out '
        timed_out_peers = []
        self.peers.update(timed_out_peers)
        for peer in timed_out_peers:
            self.removePeer(peer.id)

    def removePeer(self,id):
        'remove a peer from game, tell all remaining peers to remove his players '
        self.log.write("removing peer\n")
        peer = self.peers.getById(id)
        for player in peer.getPlayers():
            self.tellAllPeers(NetMessRemovePlayer(self.id,None,player))
        peer.kill()
        
    # ------------------
    def addNetMessage(self,net_mess):
        'add a NetMess object to my lists of NetMessages from peers/server '

        self.net_mess_lock.acquire()
        self.net_messages.append(net_mess)
        if self.is_server and net_mess[I_MESS_FROM] != None:
            self.tellAllPeers(net_mess)
        self.net_mess_lock.release()

    def ping(self,dt):
        'Handle keep alive pings to net peers'
        ping_tick = self.ping_tick
        ping_tick.tick(dt)
        if ping_tick.isSet():
            self.checkPeers()    # remove timedout peers            
            ping_tick.reset()
            self.sysTellAllPeers(NetMessPing(self.id, None))        
            self.log.write("sending ping\n")

    def reset(self,kickpeers=1):
        ' empty the incomming netmess buffer '
        self.net_mess_lock.acquire()
        #self.peers.empty()
        if kickpeers: self.peers_running.empty()
        self.net_messages = [] # reset all messages
        self.net_mess_lock.release()

    def close(self):
        self.net.close()

class ServerNetController(NetController):
    def __init__(self,game_engine,my_port):
        '''
        Server netcontroller
        '''
        NetController.__init__(self,game_engine,my_port)
        # set game engine net controller to me!
        self.log.write("Starting net comm server\n")
        self.is_server = 1
        self.is_client = 0
        self.net = NetServer(self.addNetMessage,port=my_port)
        self.net.start()

    def peerRunningGE(self,from_id):
        ''' called by peer netmess when it's ready to receve game data from server
            adds all current players to the peer and then send start game message
        '''
        self.log.write( "Peer %d Running Game\n"%from_id)
        # add all current players to new peer
        peer = self.peers.getById(from_id)
        self.peers_running.add((peer,))
        for player in self.game_engine.players.sprites():            
            if not peer.players.has(player): # dont add peers own players
                net_mess = NetMessAddPlayer(self.id,None,player)
                self.net.sendNetMessTo(peer.addr,net_mess.getList())
        # sync all positions
        self.game_engine.syncAll()

    def peerReady(self,from_id):
        ''' called by peer netmess when it's ready to receve game system commands
            sends start game message
        '''
        self.log.write("Peer %d Ready\n"%from_id)
        # add all current players to new peer
        peer = self.peers.getById(from_id)

        # Send start game to peer
        peer.send(NetMessGameStart(self.id,self.game_system.settings['game']['map']))

    def update(self):
        'run all incomming netmessages'
        self.net_mess_lock.acquire()
        # use a copy of  the net_messages array to avoid possible dead locks when entering netmessage.update() methods that dont return
        net_messages = self.net_messages[:]
        self.net_messages = [] # reset all messages
        self.net_mess_lock.release()
        for net_mess in net_messages:
            # If netmess is a syncing only update if it's newer than our last sent net_message
            if net_mess[I_RPC] == 'synchGameObject':
                peer = self.peers.getById(net_mess[I_MESS_FROM])
                if net_mess[I_LASTRECV] < peer.lastsentto:
                    continue
            #Get the callback function to call
            print net_mess[I_RPC]
            f = getattr( self.callback, net_mess[I_RPC] )
            # do the call
            apply(f,net_mess[I_ARGS:])
#            net_mess[I_RPC].doAction(self.callback)

class ClientNetController(NetController):
    def __init__(self,game_engine,my_port,rem_addr):
        '''
        Client netcontroller
        '''
        NetController.__init__(self,game_engine,my_port)
        self.log.write("Starting net comm client\n")
        self.is_server = 0
        self.is_client = 1
        self.rem_addr = rem_addr
        self.my_port = my_port

        self.net = NetServer(self.addNetMessage, port=self.my_port)
        # start recv thread
        self.net.start()
        
    def connect(self):
        ''' Connect to server '''
        self.reset()
        # add server to peers
        rem_server = Peer(self,self.rem_addr,0)
        rem_server.ready = 1

        self.peers.add((rem_server,))

        self.peers_running.add((rem_server,))

        # Register with server, and get a uniqe peer id
        self.sysTellAllPeers(NetMessAddPeer(self.getMyAddr()))
        
        # wait until we got an id or until timeout
        self.log.write("Trying to connect")

        for i in range(0,20):
            time.sleep(0.1)
            self.update()
            if self.id !=0:
                self.log.write("\n")
                return
            else: self.log.write(".")

        self.log.write("\n")
        raise self.Error("time out waiting for server respons\n")

    def update(self):
        'run all incomming netmessages'
        self.net_mess_lock.acquire()
        # use a copy of  the net_messages array to avoid possible dead locks when entering netmessage.update() methods that dont return
        net_messages = self.net_messages[:]
        self.net_messages = [] # reset all messages
        self.net_mess_lock.release()

        if len(net_messages):
            # set last receved net mess id
            self.lastrecv = net_messages[-1][I_NR]
            for net_mess in net_messages:
                #Get the callback function to call
                f = getattr( self.callback, net_mess[I_RPC] )
                # do the call
                apply(f,net_mess[I_ARGS:])
#                net_mess.doAction(self.callback)

##        self.tellAllClients(NetMessClientReady(self.id,None))

class Callback:
    ''' proxy class containing callback methods for NetMessages '''
    def __init__(self,net_controller,log):
        self.net_controller = net_controller
        self.peers = net_controller.peers
        self.game_engine = self.net_controller.game_engine
        self.game_system = self.net_controller.game_system
        self.log = log

    # Server Methods
    def addPeer(self,addr):
        self.net_controller.addPeer(addr)

    # Methods called by NetMessage objects when the doAction is invoked on object
    def startGame(self,from_id,map_name):
        self.log.write('Server started game\nmap %s\n'%map_name)
        self.game_system.n_startGame(map_name)


    def stopGame(self,from_id):
        'Tells us to stop gameengine'
        self.log.write('Server stopped game\n')
        self.game_system.n_stopGame()

    def disconnect(self,from_id):
        'Tells tells us peer disconnected'
        self.log.write('Server disconnect\n')
        self.net_controller.removePeer(from_id)
        self.game_system.n_disconnect()

    def peerReady(self,from_id):
        self.net_controller.peerReady(from_id)

    def peerRunningGE(self,from_id):
        self.net_controller.peerRunningGE(from_id)

    def ping(self,from_id):
        ' got ping form peer so set last ping time of peer'
        self.peers.getById(from_id).gotPing()
        print "GOT PING: %d" %from_id

    def setPeerId(self,id):
        ' set id of the game engine(peer) '
        self.log.write("setting id to %d\n"%id)
        self.net_controller.id = id


    def addPlayer(self,mess_from,playerargs):
        pos_x,pos_y,vel_x,vel_y,name,player_id,ship_id= playerargs
        self.log.write("Adding Player id %d,on Peer id:%d\n"%(player_id,mess_from))
        self.log.write("---------------------------------\n")
        ship = Ship(self.game_engine,pos_x,pos_y,vel_x,vel_y,id = ship_id)
        player = Player(self.game_engine, ship,name=name,id=player_id)
        peer = self.peers.getById(mess_from)
        peer.players.add((player,))
        self.game_engine.addPlayer(player,local=0)

    def removePlayer(self,mess_from,id):
        self.log.write("Removing Player id:%d\n"%id)
        print "---------------------------------\n"
        if self.game_engine.players.has(id):
            player = self.game_engine.players.getById(id)
            self.game_engine.removePlayer(player,local=0)
        else:
            self.log.write("no Player with id: %d\n" % id)

    def respawnPlayer(self,id):
        if self.game_engine.players.has(id):
            player = self.game_engine.players.getById(id)
            self.game_engine.respawnPlayer(player,local=0)
        else:
            self.log.write("no Player with id: %d\n" % id)
        

    def addShot(self,shot_class,args,key_args={}):
        shot = self.game_engine.go_factory.getGameObject(shot_class,args,key_args)
        self.game_engine.addShot(shot, local=0)

    def removeGo(self,mess_from,id):
        if self.game_engine.game_objects.has(id):
            self.game_engine.removeGo(self.game_engine.game_objects.getById(id),local=0)
        else:
            self.log.write("no Go with id: %d\n" % id)
            print self.game_engine.all_sprites.sprites()

    def shipKilled(self, id):
        ''' kill the ship '''
        if self.game_engine.game_objects.has(id):
            ship = self.game_engine.game_objects.getById(id)
            self.game_engine.shipKilled(ship,local=0)
        else:
            self.log.write("no Game object with id: %d\n" % id)
            
    def synchGameObject(self, id, sync_args,pos_only=0):
        ''' Sync the GameObject wich has the id, if pos_only=1 then only sync GameObject pos info '''
        if self.game_engine.game_objects.has(id):
            game_object = self.game_engine.game_objects.getById(id)
            if pos_only: 
                game_object.syncPos(sync_args)
            else:
                game_object.sync(sync_args)
        else:
            self.log.write("no Game object with id: %d\n" % id)

    def getGameMap(self):
        return self.game_engine.game_map


#---------------------------------------------------------------
# Peer
#---------------------------------------------------------------
class Peer(Sprite):
    ID = 0
    'Object represents a Network peer, holds track of ping, timeout, address, players etc.'
    def __init__(self,net_controller,addr,id):
        Sprite.__init__(self) #call Sprite initializer        
        self.net_controller = net_controller
        self.net = net_controller.net
        self.timeout = 10
        self.addr = addr
        self.id = id
        self.players = Group(())
        self.last_ping = (pygame.time.get_ticks()/1000.000)
        self.ready = 0 # set this flag if peer is ready to recive game data
        self.lastsentto = 0
        
    def update(self,timed_out_peers):
        ' check if peer has timed out and add it to timed_out list if It has'
        if self.timeout < ((pygame.time.get_ticks()/1000.000)-self.last_ping):
            print "peer timed out %d"%self.id
            timed_out_peers.append(self)

    def kill(self):
        for player in self.players.sprites():
            print "player killed!"
            player.kill()
        Sprite.kill(self)

    def getPlayers(self):
        return self.players.sprites()

    def gotPing(self):
        self.last_ping = (pygame.time.get_ticks()/1000.000)

    def send(self,net_mess):
        self.lastsentto += 1
        if not (type(net_mess) is ListType):
            net_mess.nr = self.lastsentto
            net_mess.lastrecv = self.net_controller.lastrecv
            net_mess = net_mess.getList()
        self.net.sendNetMessTo(self.addr,net_mess)

#---------------------------------------------------------------
# Net Message Objects
#---------------------------------------------------------------

